package query_understanding.query_understand.tree_generator;

import com.ambiverse.api.model.Entity;
import common.helper.json.JavaToJsonString;
import edu.stanford.nlp.ling.IndexedWord;
import edu.stanford.nlp.semgraph.SemanticGraph;
import edu.stanford.nlp.semgraph.SemanticGraphEdge;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import query_understanding.common.nlp_toolkit.DependencyParser;
import query_understanding.common.nlp_toolkit.RelationEdge;
import query_understanding.common.nlp_toolkit.RelationPath;
import query_understanding.common.nlp_toolkit.RelationTriplePath;
import query_understanding.query_language.doc_entity_tree.EntityQueryTree;
import query_understanding.query_language.doc_entity_tree.QueryEdge;
import query_understanding.query_language.doc_entity_tree.QueryNode;
import query_understanding.query_language.doc_entity_tree.QueryNodeTokenMap;
import query_understanding.query_understand.tree_progress.DocumentQueryProgress;
import query_understanding.query_understand.tree_progress.EntityQueryProgress;
import semantic_compute.entity_linking.schema.EntityLinkingResult;
import semantic_compute.relation_extraction.schema.RelationMatchResult;
import semantic_compute.relation_extraction.yago_matcher.YagoRelationMatcher;

import java.util.*;

/**
 * Created by julianzliu on 4/22/2017.
 */
//@Component
public class CoreChainIdentifier {

    static final Logger logger = LoggerFactory.getLogger(CoreChainIdentifier.class);

    private DocumentQueryProgress progress;
    private EntityQueryProgress entityProgress;

    private YagoRelationMatcher yagoRelationMatcher;
    private List<RelationTriplePath> relations;

    public CoreChainIdentifier(DocumentQueryProgress progress,EntityQueryProgress entityProgress) {
        this.entityProgress = entityProgress;
        this.progress = progress;
    }

    public void setYagoRelationMatcher(YagoRelationMatcher matcher){
        this.yagoRelationMatcher = matcher;
    }


    /*******************************************************************************************
     * 入口函数
     * @return
     */
    public DocumentQueryProgress identifyCoreReferenceChain(){
        /*
        目前最多增广长度为2的关系链
          对于本文定义的多种增广单个关系的方法，分别
         */
        PriorityQueue<EntityQueryTree> treePriorityQueue = this.entityProgress.treePriorityQueue;
        PriorityQueue<EntityQueryTree> newTreeQueue = this.entityProgress.genEmptyPriorityQueue();


        for(EntityQueryTree old_tree: treePriorityQueue){
            /*
            增长每棵树
             */
            // 1) 长度为2的链
            Map<RelationTriplePath, Map<String,Set<String>> > lengthTwoRelations = extractLengthTwoRelation( old_tree );
            List<EntityQueryTree> lengthTwoTrees = growLengthTwoTrees(old_tree, lengthTwoRelations);
            for(EntityQueryTree new_tree: lengthTwoTrees){
                newTreeQueue.add( new_tree );
            }

            // 2） 长度为1的链
            //if(lengthTwoRelations.size() > 0)
            Map<RelationTriplePath,Set<String> > lengthOneRelations = extractLengthOneRelation( old_tree );
            List<EntityQueryTree> lengthOneTrees = growLengthOneTrees(old_tree, lengthOneRelations);
            for(EntityQueryTree new_tree: lengthOneTrees){
                newTreeQueue.add( new_tree );
            }
        }


        this.entityProgress.treePriorityQueue = newTreeQueue;
        this.entityProgress.extendFinishedQueue();
        return this.progress;
    }



    private List<RelationTriplePath> extractAllPathFromDependencyTree(EntityQueryTree entityQueryTree){
        //List<RelationPath>  relations = DependencyParser.extractLengthTwoRelation(dependencies,current,visited );
        SemanticGraph dependencies = this.entityProgress.getDependencies();
        IndexedWord current = ((QueryNode.EntityNode) entityQueryTree.topicEntity).mainToken; //entityQueryTree.curret.mainToken;

        Set<IndexedWord> visitedNodes = new HashSet<>(entityQueryTree.visited);
        Set<SemanticGraphEdge> visitedEdges = new HashSet<>();
        List<RelationTriplePath> relations = DependencyParser.extractAllPossiblePath( dependencies,current,visitedNodes,visitedEdges);
        System.out.println("[extractAllPathFromDependencyTree] size = " + relations.size());
        return relations;
    }



















    /******************************************************************************************/
    /*********
     * 为依存树中抽取的每条关系链，扩展一棵新树。
     * 依存树抽取的关系链  与  Yago识别的关系链的对应
     * @param lengthTwoRelations
     * @return
     */
    public List<EntityQueryTree> growLengthTwoTrees( EntityQueryTree old_tree, Map<RelationTriplePath, Map<String,Set<String>> > lengthTwoRelations){
        List<EntityQueryTree> new_trees = new ArrayList<>();
        for(RelationTriplePath relationPath :lengthTwoRelations.keySet()){
            Map<String,Set<String>> matchedRelations = lengthTwoRelations.get(relationPath);

            EntityQueryTree new_tree = growLengthTwoTree(old_tree,relationPath, matchedRelations);
            if(new_tree != null)
                new_trees.add( new_tree );
        }

        return new_trees;
    }


    /***************
     * 根据 核心关系链 抽取结果，生成一棵树
     * 目前只生成一棵 ！！！
     * @param relationPath
     * @param matchedRelations
     * @return
     */
    private EntityQueryTree growLengthTwoTree(EntityQueryTree old_tree, RelationTriplePath relationPath, Map<String,Set<String>> matchedRelations){
        Pair<String,String> yagoPath = selectBestLengthTwoEdge( matchedRelations );
        RelationEdge.BaseRelation relation = relationPath.path.get(0);
        if(yagoPath == null){
            /*
            生成一棵带两个 occur-with 边的树吗？
            --》 不生成，当长度为1的边与无法成功匹配路径的时候，
                 再生成长度为1的occur-with边
             */
            return null;
        }
        else{
            System.out.println("[growLengthTwoTree] yagoPath = " + yagoPath.toString());
            EntityQueryTree new_tree = old_tree.deepCopyTree();//new EntityQueryTree( old_tree );
            new_tree.treeScore = 4*old_tree.treeScore;
            QueryNode.EntityNode root = (QueryNode.EntityNode)new_tree.topicEntity;

            QueryEdge.YagoEdge edge1 = new QueryEdge.YagoEdge();
            edge1.nodesSet.add( root );
            edge1.yago_label = yagoPath.getKey();
            edge1.mainToken = relationPath.path.get(0).relation;
            new_tree.addRelation(  relationPath.path.get(0) );
            // TODO: should add all tokens
            new_tree.addIndexWord( edge1.mainToken, edge1 );
            root.edgesSet.add( edge1 );

            /*
            QueryNode.VariableNode var1 = new QueryNode.VariableNode();
            var1.edgesSet.add( edge1 );
            edge1.nodesSet.add( var1 );
            */
            QueryNode.BaseNode node1 = addOtherSideNode(new_tree,relationPath.path.get(0),edge1);

            QueryEdge.YagoEdge edge2 = new QueryEdge.YagoEdge();
            edge2.yago_id = null;
            edge2.yago_label = yagoPath.getValue();
            edge2.mainToken = relationPath.path.get(1).relation;
            new_tree.addRelation(  relationPath.path.get(1) );
            // TODO: should add all tokens
            new_tree.addIndexWord( edge2.mainToken, edge2 );
            edge2.nodesSet.add( node1 );
            node1.edgesSet.add(edge2);


            /*
            QueryNode.VariableNode var2 = new QueryNode.VariableNode();
            var2.edgesSet.add( edge2 );
            edge2.nodesSet.add( var2 );
            */

            //new_tree.answerEntity = var2;

            QueryNode.BaseNode var2 = addOtherSideNode( new_tree, relationPath.path.get(1), edge2);

            System.out.println("[growLengthTwoTree] successfully grow a tree! Two Edge\n\n");
            EntityQueryProgress.deepFirstTraversal( new_tree );
            EntityQueryProgress.deepFirstTraversalFromNode(new_tree, var2);
            return new_tree;

        }
        //return null;
    }


    /**************
    应该以 Map< Pair<String, String>  , Double > 存储每个关系链的得分
    先假设只会匹配一条关系链，
    选择出现的第一条。。。。。
     */
    public Pair<String,String> selectBestLengthTwoEdge(Map<String,Set<String>> matchedRelations){
        System.out.println("\n\n\n[selectBestLengthTwoEdge] selecting ....");
        System.out.println(JavaToJsonString.mapJavaToJson(matchedRelations) );
        boolean isFound = false;
        Pair<String,String> pair = null;
        for(String edge1: matchedRelations.keySet()){
            if(isFound) break;
            Set<String> edge2Set = matchedRelations.get( edge1);
            for(String edge2 : edge2Set){
                if(isFound) break;
                pair = new Pair<>(edge1, edge2);
                isFound = true;
            }
        }
        return pair;
    }


    /********************************************************************************************
     *
     * @param lengthOneRelations
     * @return
     */
    public List<EntityQueryTree> growLengthOneTrees( EntityQueryTree old_tree, Map<RelationTriplePath,Set<String> > lengthOneRelations){
        List<EntityQueryTree> new_trees = new ArrayList<>();
        for(RelationTriplePath relationPath: lengthOneRelations.keySet()){
            Set<String> matchedRelations = lengthOneRelations.get( relationPath );

            EntityQueryTree new_tree = growLengthOneTree(old_tree,relationPath, matchedRelations);

            if(new_tree != null){
                new_trees.add( new_tree );
            }
        }
        return new_trees;
    }

    public EntityQueryTree growLengthOneTree(EntityQueryTree old_tree, RelationTriplePath relationPath, Set<String> matchedRelations){
        String yagoPath = selectBestLengthOneEdge(matchedRelations);
        System.out.println("[growLengthOneTree] yagoPath = " + yagoPath);
        RelationEdge.BaseRelation relation = relationPath.path.get(0);

        if(yagoPath == null){
            /*
            创建 occur-with 边
             */
            EntityQueryTree new_tree = old_tree.deepCopyTree();//new EntityQueryTree( old_tree );
            new_tree.treeScore = 1.5*old_tree.treeScore;
            QueryNode.EntityNode root = (QueryNode.EntityNode)new_tree.topicEntity;

            /*
            TODO: 应该考虑整个短语
             */
            QueryEdge.OccurEdge edge1 = new QueryEdge.OccurEdge();
            edge1.text = relation.relation.word();
            edge1.mainToken = relation.relation;
            new_tree.addRelation( relation );
            new_tree.addIndexWord( edge1.mainToken,edge1 );
            edge1.nodesSet.add( root );
            root.edgesSet.add( edge1 );

            addOtherSideNode(new_tree, relation, edge1);



            //EntityQueryProgress.deepFirstTraversal( new_tree );
            return new_tree;
        }
        else{
            EntityQueryTree new_tree = old_tree.deepCopyTree();//new EntityQueryTree( old_tree );
            new_tree.treeScore = 2*old_tree.treeScore;
            QueryNode.EntityNode root = (QueryNode.EntityNode)new_tree.topicEntity;

            QueryEdge.YagoEdge edge1 = new QueryEdge.YagoEdge();
            edge1.mainToken = relation.relation;
            new_tree.addRelation( relation );
            new_tree.addIndexWord( edge1.mainToken, edge1 );
            edge1.yago_label = yagoPath;
            edge1.nodesSet.add( root );
            root.edgesSet.add( edge1 );

            addOtherSideNode(new_tree, relation, edge1);
            /*
            QueryNode.VariableNode var1 = new QueryNode.VariableNode();
            var1.edgesSet.add( edge1 );
            edge1.nodesSet.add( var1 );
            new_tree.answerEntity = var1;
            */

            System.out.println("[growLengthOneTree] successfully grow a tree!\n\n");
            //EntityQueryProgress.deepFirstTraversal( new_tree );
            return new_tree;
        }
    }


    public QueryNode.BaseNode addOtherSideNode(EntityQueryTree new_tree, RelationEdge.BaseRelation relation, QueryEdge.BaseEdge edge){
        QueryNode.BaseNode node = null;
        /**************************************************
         宾语
         */
        // 为空,先生成变量节点
        // 最后再看，作为叶子节点的变量节点变为空关键词节点
        if( !relation.isHaveObject() ){
            // 非Yago关系 ，建立空文本节点
            /*
            if(edge.edgeType == QueryEdge.EdgeType.OCCUR_EDGE){
                QueryNode.KeywordNode var1 = new QueryNode.KeywordNode();
                // 不设置文本值-》设为空值
                var1.edgesSet.add( edge );
                edge.nodesSet.add( var1 );

                new_tree.answerEntity = var1;

                node = var1;
            }
            */
            /*
            TODO： 目前没有区分 type 关系  和  yago 关系
             */
            //else{
                QueryNode.VariableNode var1 = new QueryNode.VariableNode();
                var1.edgesSet.add( edge );
                edge.nodesSet.add( var1 );
                new_tree.answerEntity = var1;

                node = var1;

                new_tree.addIndexWord(relation.otherSide, var1);
            //}

        }
        else {
            IndexedWord other = relation.otherSide;
            // Entity Node
            if(this.entityProgress.wordEntityMap.containsKey(other) ){
                EntityLinkingResult.LinkedEntity mention = this.entityProgress.wordEntityMap.get( other );
                QueryNodeTokenMap nodeTokenMap = this.entityProgress.entityTokenMap.get(other);
                QueryNode.EntityNode entityNode = new QueryNode.EntityNode();
                entityNode.entity = mention;
                entityNode.score = mention.score;
                entityNode.mainToken = nodeTokenMap.mainToken;
                entityNode.allTokens = nodeTokenMap.tokenSet;
                entityNode.yago_id = this.progress.entityLinkingResult.entityMetadata.get( mention.entity ).YAGO_ID;
                entityNode.yago_label = entityNode.yago_id;

                entityNode.edgesSet.add( edge );
                edge.nodesSet.add( entityNode );

                //new_tree.addQueryNodeTokenMapOfNode( nodeTokenMap );
                new_tree.addIndexWord(other, entityNode);
                new_tree.answerEntity = entityNode;

                node = entityNode;
            }
            /*
             疑问词，从句词等 -> 变量节点
             TODO ： 目前，只要不是实体的都被设置为变量节点了，以后还需要考虑类型节点
              */
            else {//if( DependencyParser.queryPosSet.contains( other.tag() )){
                QueryNode.VariableNode var1 = new QueryNode.VariableNode();
                var1.edgesSet.add( edge );
                edge.nodesSet.add( var1 );

                new_tree.answerEntity = var1;


                new_tree.addIndexWord(relation.otherSide, var1);

                //QueryNodeTokenMap nodeTokenMap = new QueryNodeTokenMap();
                //nodeTokenMap.mainToken = relation.otherSide;

                //new_tree.addQueryNodeTokenMapOfNode( nodeTokenMap );

                node = var1;
            }
        }
        return node;
    }



    public String selectBestLengthOneEdge(Set<String> matchedRelations){
        String best = null;
        boolean found = false;
        for(String edge : matchedRelations){
            if(found == true) break;
            best = edge;
            found  = true;
        }
        return best;
    }







    /******************************************************************************************/


    public String genRelationStr(EntityQueryTree entityQueryTree, RelationEdge.BaseRelation relation){   //Pair<SemanticGraphEdge,IndexedWord> relation){
        String relation_str = relation.relation.word();
        return relation_str;
    }


    public List< Pair<String,Long> > getCanditateRelations(String relation_str){
        List<Pair<String,Long>> relations = this.yagoRelationMatcher.matchSingleRelation( relation_str );
        for(Pair<String, Long> pair: relations){
            System.out.println(String.format("----->>>>>> " + pair.getKey() + " " + pair.getValue()  ));
        }
        return relations;
    }

    public List<String> getCanditateEdges(List<Pair<String,Long>> relations){
        List<String > edges = new ArrayList<>();
        for(Pair<String, Long> pair:relations){
            edges.add( pair.getKey() );
        }
        return edges;
    }





    /************************************************************************************************
     * length 1
     * @param entityQueryTree
     */
    public Map<RelationTriplePath, Set<String> > extractLengthOneRelation(EntityQueryTree entityQueryTree){
        String topic_entity = ((QueryNode.EntityNode)entityQueryTree.topicEntity).yago_label;
        //SemanticGraph dependencies = this.entityProgress.getDependencies();
        IndexedWord current = ((QueryNode.EntityNode) entityQueryTree.topicEntity).mainToken;//entityQueryTree.curret.mainToken;
        /*
        需要创建临时的标记集，
        因为真正的树还没有生成。
         */
        //Set<IndexedWord> visited = new HashSet<>(entityQueryTree.visited);
        //List< Pair<SemanticGraphEdge,IndexedWord> >  relations = DependencyParser.extractLengthOneRelation(dependencies,current,visited );

        List<RelationTriplePath> relations = extractAllPathFromDependencyTree( entityQueryTree );

        System.out.println("\n\n[extractLengthOneRelation] all relations --------->>>>>>>>>>>");
        for(RelationTriplePath relation : relations){
            //if(relation.path.size() != 1) continue;
            System.out.println( relation.toString() );
        }
        System.out.println("\n<<<<--------------------RELATION--------------------------------\n");

        Map<RelationTriplePath, Set<String> > lengehOneRelations = new HashMap<>();

        // 对每个抽取出来的边，与知识库进行匹配
        for(RelationTriplePath relation : relations){
            if(relation.path.size() != 1) continue;
            //RelationPath relationPath = new RelationPath();
            //relationPath.startNode = current;
            //relationPath.path.add(relation);

            // a）获取文本表示
            String relation_str = genRelationStr( entityQueryTree, relation.path.get(0));
            // b) 到知识图谱中匹配
            List<RelationMatchResult> results = matchLengthOneRelation( topic_entity, relation_str);
            Set<String> yagoRelations = deleteDuplicatedLengthOneResult(results);

            lengehOneRelations.put( relation, yagoRelations );
        }
        return lengehOneRelations;
    }


    public Set<String>  deleteDuplicatedLengthOneResult( List<RelationMatchResult> results ){
        Set<String> relations = new HashSet<>();
        for(RelationMatchResult result: results){
            relations.add( result.edges.get(0).getProperty("yago_label") );
        }
        return relations;
    }


    public List<RelationMatchResult> matchLengthOneRelation(String vertex, String relation_str){
        System.out.println("[matchLengthOneRelation] Vertex = " + vertex);

        // 1) Candicate
        List<Pair<String,Long>> relations = getCanditateRelations(relation_str);

        // 2) Filter
        List<String> edges = getCanditateEdges(relations);
        List<RelationMatchResult> matchResults = this.yagoRelationMatcher.matchLengthOnePath(vertex,edges);

        return matchResults;
    }


    /********************************************************************************************************
     * length 2
     * @param entityQueryTree
     */
    public Map<RelationTriplePath, Map<String,Set<String>> > extractLengthTwoRelation(EntityQueryTree entityQueryTree){
        String topic_entity = ((QueryNode.EntityNode)entityQueryTree.topicEntity).yago_id;
        System.out.println("[extractLengthTwoRelation] topic_entity = " + topic_entity);

        List<RelationTriplePath> relations = extractAllPathFromDependencyTree( entityQueryTree );


        System.out.println("\n\n[extractLengthTwoRelation] all relations --------->>>>>>>>>>>");
        for(RelationTriplePath relationPath : relations){
            String pathPrint = relationPath.toString();
            System.out.println(pathPrint);
        }
        System.out.println("\n<<<<--------------------RELATION--------------------------------\n");

        //List<Map<String,Set<String>>> lengthTwoRelations = new ArrayList<>();
        Map< RelationTriplePath, Map<String,Set<String>> >  lengthTwoRelations  = new HashMap<>();


        // 对每个抽取出来的边，与知识库进行匹配
        for(RelationTriplePath relationPath : relations){
            if(relationPath.path.size() != 2) {
                logger.info("size not equal to 2, ignore...  {}", relationPath);
                continue;
            }
            /*
            剪枝 -》 如果第二条链的另一端是实体，则暂时不考虑这条链
             */
            /*
            IndexedWord word = relationPath.path.get(1).otherSide;
            if( checkWhetherNeighborOfEntity( word ) == true ){
                System.out.println("second [] edge linked to anthor entity, ignore");
                continue;
            }
            */

            // a）获取文本表示
            String relation_str1 = genRelationStr( entityQueryTree, relationPath.path.get(0));
            String relation_str2 = genRelationStr( entityQueryTree, relationPath.path.get(1));

            // b) 到知识图谱中匹配
            List<RelationMatchResult> results = matchLengthTwoRelation( topic_entity, relation_str1, relation_str2);
            Map<String,Set<String>> yagoRelations = deleteDuplicatedLengthTwoResult( results );
            //lengthTwoRelations.add(yagoRelations);
            /*
            for(String edge1 : yagoRelations.keySet()){
                Set<String> edge2Set;
                if(lengthTwoRelations.containsKey( edge1 ))
                    edge2Set = lengthTwoRelations.get( edge1 );
                else {
                    edge2Set = new HashSet<>();
                    lengthTwoRelations.put(edge1, edge2Set);
                }
                edge2Set.addAll( yagoRelations.get(edge1) );
            }
            */
            //if(yagoRelations != null)
            lengthTwoRelations.put(relationPath, yagoRelations);
        }
        System.out.println( "Size Of Matched Yago Path = " + lengthTwoRelations.size() + "\n\n\n\n");
        return lengthTwoRelations;
    }


    private boolean checkWhetherNeighborOfEntity(IndexedWord word){
        return false;
    }






    public Map<String,Set<String>>  deleteDuplicatedLengthTwoResult( List<RelationMatchResult> results ){
        System.out.println("\n\n\n[deleteDuplicatedLengthTwoResult]");
        Map<String,Set<String>> relations = new HashMap<>();
        boolean foundOne = false;
        for(RelationMatchResult result: results){
            foundOne = true;
            String edge1 = result.edges.get(0).getProperty("yago_label");
            Set<String> relationSet;
            if(relations.containsKey(edge1))
                relationSet = relations.get(edge1);
            else {
                relationSet = new HashSet<>();
                relations.put(edge1, relationSet);
            }
            String edge2 = result.edges.get(1).getProperty("yago_label");
            relationSet.add( edge2 );
            System.out.println("[deleteDuplicatedLengthTwoResult] edge1 = " + edge1 + " , edge2 = " + edge2);
        }
        //if(foundOne == false) return null;
        return relations;
    }


    public List<RelationMatchResult> matchLengthTwoRelation(String vertex, String relation_str1, String relation_str2){
        System.out.println("[matchLengthTwoRelation] Vertex = " + vertex);

        // 1) Candicate
        List<Pair<String,Long>> relations1 = getCanditateRelations(relation_str1);
        List<Pair<String,Long>> relations2 = getCanditateRelations(relation_str2);

        // 2) Filter
        List<String> edges1 = getCanditateEdges(relations1);
        List<String> edges2 = getCanditateEdges(relations2);
        List<RelationMatchResult> matchResults = this.yagoRelationMatcher.matchLengthTwoPath(vertex,edges1, edges2);

        return matchResults;
    }



    /*****************************************************************************************************************
     *
     * @param args
     */
    public static void main(String[] args){

    }
}
